/*
 *  This file is part of AtlasMapper server and clients.
 *
 *  Copyright (C) 2012 Australian Institute of Marine Science
 *
 *  Contact: Gael Lafond <g.lafond@aims.org.au>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package au.gov.aims.metadatagenerator.tools;

import au.gov.aims.metadatagenerator.wkt.WktBoundingBox;
import au.gov.aims.metadatagenerator.wkt.WktLine;
import au.gov.aims.metadatagenerator.wkt.WktPoint;
import au.gov.aims.metadatagenerator.wkt.WktPolygon;
import org.apache.velocity.tools.config.DefaultKey;

import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

// This tool is named "wktParser" in velocity templates
//     $wktParser.parse(...)
// See: http://en.wikipedia.org/wiki/Well-known_text
@DefaultKey("wktParser")
public class WktParser {

	public static WktBoundingBox calculateBbox(List<String> wkts) {
		if (wkts == null || wkts.isEmpty()) {
			return null;
		}

		WktBoundingBox boundingBox = new WktBoundingBox();
		for (String wkt : wkts) {
			wkt = wkt.trim();
			if (wkt.startsWith("POINT")) {
				WktPoint point = parsePoint(wkt);
				boundingBox.updateBound(point);
			} else if (wkt.startsWith("LINESTRING")) {
				WktLine line = parseLineString(wkt);
				boundingBox.updateBound(line);
			} else if (wkt.startsWith("POLYGON")) {
				WktPolygon polygon = parsePolygon(wkt);
				boundingBox.updateBound(polygon);

			} else if (wkt.startsWith("MULTIPOINT")) {
				List<WktPoint> points = parseMultiPoint(wkt);
				boundingBox.updateBound(points);
			} else if (wkt.startsWith("MULTILINESTRING")) {
				List<WktLine> lines = parseMultiLineString(wkt);
				boundingBox.updateBound(lines);
			} else if (wkt.startsWith("MULTIPOLYGON")) {
				List<WktPolygon> polygons = parseMultiPolygon(wkt);
				boundingBox.updateBound(polygons);
			}
		}

		return boundingBox;
	}

	// Parse a WKT expression and return a String representing the value.
	// NOTE: MultiPolygons, MultiPoints, MultiLines may return multiple values. (TODO)
	public static List<String> parse(String wkt) {
		if (wkt == null || wkt.isEmpty()) {
			return null;
		}

		wkt = wkt.trim();
		if (wkt.startsWith("POINT")) {
			WktPoint point = parsePoint(wkt);
			return null; // TODO
		} else if (wkt.startsWith("LINESTRING")) {
			WktLine line = parseLineString(wkt);
			return null; // TODO
		} else if (wkt.startsWith("POLYGON")) {
			WktPolygon polygon = parsePolygon(wkt);
			return polygon.toLinearRing();

		} else if (wkt.startsWith("MULTIPOINT")) {
			List<WktPoint> points = parseMultiPoint(wkt);
			return null; // TODO
		} else if (wkt.startsWith("MULTILINESTRING")) {
			List<WktLine> lines = parseMultiLineString(wkt);
			return null; // TODO
		} else if (wkt.startsWith("MULTIPOLYGON")) {
			List<WktPolygon> polygons = parseMultiPolygon(wkt);
			return null; // TODO
		}

		return null;
	}

	// POINT (30 10)
	public static WktPoint parsePoint(String wkt) {
		return null; // TODO
	}

	// LINESTRING (30 10, 10 30, 40 40)
	public static WktLine parseLineString(String wkt) {
		return null; // TODO
	}

	// Input:
	//     POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))
	// Output:
	//     "30 10 40 40 20 40 10 20 30 10"
	//
	// Input:
	//     POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))
	// Output:
	//     "35 10 45 45 15 40 10 20 35 10", "20 30 35 35 30 20 20 30"
	//
	public static WktPolygon parsePolygon(String wkt) {
		WktPolygon wktPolygon = new WktPolygon();

		// wkt = "POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))"
		Matcher polygonsMatcher = Pattern.compile("^[^\\(]*\\((.*)\\)[^\\)]*$", Pattern.CASE_INSENSITIVE | Pattern.DOTALL).matcher(wkt);
		while (polygonsMatcher.find()){
			// polygons = "(35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30)"
			String polygons = polygonsMatcher.group(1).trim();

			Matcher polygonMatcher = Pattern.compile("\\(([^\\(\\)]*)\\)", Pattern.CASE_INSENSITIVE | Pattern.DOTALL).matcher(polygons);
			while (polygonMatcher.find()){
				WktPolygon.WktPolygonPart wktPolygonPart = new WktPolygon.WktPolygonPart();
				// polygon = "35 10, 45 45, 15 40, 10 20, 35 10"
				String polygon = polygonMatcher.group(1).trim();

				// coordinates = ["35 10", "45 45", "15 40", "10 20", "35 10"]
				String[] coordinates = polygon.split(",");
				for (String coordinate : coordinates) {
					// coordinate = "35 10"
					// values = ["35", "10"]
					String[] values = coordinate.trim().split("\\s");
					if (values.length >= 2) {
						// 2 = lon, lat
						// 3 = lon, lat, altitude
						wktPolygonPart.addPoint(new WktPoint(
								Double.valueOf(values[0].trim()),
								Double.valueOf(values[1].trim())
						));
					}
				}

				if (!wktPolygonPart.isEmpty()) {
					wktPolygon.addPart(wktPolygonPart);
				}
			}
		}

		return wktPolygon;
	}

	// MULTIPOINT ((10 40), (40 30), (20 20), (30 10))
	// MULTIPOINT (10 40, 40 30, 20 20, 30 10)
	public static List<WktPoint> parseMultiPoint(String wkt) {
		return null; // TODO
	}

	// MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))
	public static List<WktLine> parseMultiLineString(String wkt) {
		return null; // TODO
	}

	// MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))
	// MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20)))
	public static List<WktPolygon> parseMultiPolygon(String wkt) {
		return null; // TODO
	}
}
